iT邦幫忙

7

匿名函數遞迴

  • 分享至 

  • xImage
  •  

遞迴是在函數中呼叫自己形成的,但是匿名函數沒有名字,要怎麼讓它遞迴?最近比較有空了,所以來試試看,也稍微練練手,究竟很久沒寫文。(其實是因為快被火了,就做到月底,所以現在給的任務比較少...)

先用階乘來做簡單的例子。

function test1() {
    console.log(fact(5));
    function fact(n) {
        return n < 2 ? 1 : n * fact(n-1);
    }
}
test1();//print 120

如果要把fact改成匿名函數的形式,需要做一些改造,讓它可以傳入一個函數參數,然後用這個它構成遞迴。在這之前,先把它咖喱化:

function test2() {
    console.log(fact()(5));
    function fact() {
        return function(n) {
            return n < 2 ? 1 : n * fact()(n-1);
        };
    }
}
test2();//print 120

沒問題的話,進行下一步改造:

function test3() {
    console.log(fact(fact)(5));
    function fact(fact) {
        return function(n) {
            return n < 2 ? 1 : n * fact(fact)(n-1);
        };
    }
}
test3();//print 120

這樣,在函數內就可以透過傳入的參數來遞迴。其實在函數內,參數名字不需要叫做fact

function test4() {
    console.log(fact(fact)(5));
    function fact(x) {
        return function(n) {
            return n < 2 ? 1 : n * x(x)(n-1);
        };
    }
}
test4();//print 120

但是外部呼叫函數時還是帶著fact,我們可以用另一個函數來處理來去掉對名字的依賴,這樣才真正可以變成匿名函數:

function test5() {
    console.log(Y(function(x) {
        return function(n) {
            return n < 2 ? 1 : n * x(x)(n-1);
        };
    })(5));
    function Y(f) {
        return f(f);
    }
}
test5();//print 120

這樣,透過一個簡單的外部函數Y,就可以讓匿名函數也可以構成遞迴。現在問題是,在函數裡面還是留著x(x),這跟我們一般寫遞迴函數的寫法不一樣。如果要盡量接近第一個例子,可以把處理過程也移進Y

function test6() {
    console.log(Y(function(x) {
        return function(n) {
            return n < 2 ? 1 : n * x(n-1);
        };
    })(5));
    function Y(f) {
        return function(x) {
            return x(x);
        }(function(x) {
            return f(function(n) {
                return x(x)(n);
            });
        });
    }
}
test6();//print 120

這個Y函數,聽說就是有名的Y Combinator,裡面的推導過程,可以參考良葛格的文章:Y Combinator,或是從Wiki看到的Javascript例子:The Y Combinator explained with JavaScript,我不學無術就不多廢話。

接下來才是我真正想做的東西,把Y Combinator用在Mutual Recursion...因為網路上找不到Javascript的例子,所以只好自己做。前面的過程是為了幫助自己理解,不然不知道怎改。

把Y函數擴充一下,讓它接受兩個參數,底下的結構也一併擴充,就可以讓兩個匿名函數互相呼叫構成遞迴,改名為Ys(據說可以做Mutual Recursion的叫做Y*):

function test7() {
    console.log(Ys(function(x) {
        return function(n) {
            return n < 2 ? 1 : n * x(n-1);
        };
    }, function(x) {
        return function(n) {
            return n < 2 ? 1 : n * x(n-1);
        };
    }));
    function Ys(f, g) {
        return function(x, y) {
            return x(y, x);
        }(function(x, y) {
            return f(function(n) {
                return x(y, x)(n);
            });
        }, function(x, y) {
            return g(function(n) {
                return x(y, x)(n);
            });
        });
    }
}
test7();//print 120

傳入的兩個函數其實完全一樣,好像看不出到底有沒有構成Mutual Recursion...反正先求會動,再求有用。

用另外一對函數來試試看:

function test8() {
    let fi = Ys(function(even) {
        return function(n) {
            if(n < 2) return 'odd';
            else return even(n-1);
        };
    }, function(odd) {
        return function(n) {
            if(n < 2) return 'even';
            else return odd(n-1);
        };
    });
    console.log(6, fi(6));
    console.log(7, fi(7));
    function Ys(f, g) {
        return function(x, y) {
            return x(y, x);
        }(function(x, y) {
            return f(function(n) {
                return x(y, x)(n);
            });
        }, function(x, y) {
            return g(function(n) {
                return x(y, x)(n);
            });
        });
    }
}
test8();
//print 6 even
//print 7 odd

看起來是對了。不過因為我需要的是先把函式庫內的特定函數處理過,之後再拿使用者提供的函數來互相遞迴,所以把它做一下咖喱化:

function test9() {
    let fi = Ys(function(even) {
        return function(n) {
            if(n < 2) return 'odd';
            else return even(n-1);
        };
    })(function(odd) {
        return function(n) {
            if(n < 2) return 'even';
            else return odd(n-1);
        };
    });
    console.log(6, fi(6));
    console.log(7, fi(7));
    function Ys(f) {
        return function(g) {
            return function(x) {
                return function(y) {
                    return x(y)(x);
                };
            }(function(x) {
                return function(y) {
                    return f(function(n) {
                        return x(y)(x)(n);
                    });
                };
            })(function(x) {
                return function(y) {
                    return g(function(n) {
                        return x(y)(x)(n);
                    });
                };
            });
        }
    }
}
test9();
//print 6 even
//print 7 odd

想要再酷炫一點,可以把Ys用arraw function改寫:

function test10() {
    let Ys = f=>g=>(x=>y=>x(y)(x))(x=>y=>f(n=>x(y)(x)(n)))(x=>y=>g(n=>x(y)(x)(n)));
    let fi = Ys(function(even) {
        return function(n) {
            if(n < 2) return 'odd';
            else return even(n-1);
        };
    })(function(odd) {
        return function(n) {
            if(n < 2) return 'even';
            else return odd(n-1);
        };
    });
    console.log(6, fi(6));
    console.log(7, fi(7));
}
test10();
//print 6 even
//print 7 odd

恩,改寫過就跟密碼差不多了XD

其實講到Mutual Recursion跟Y Combinator,會查到一篇文章:Many faces of the fixed-point combinator...但是我看不太懂(符號也不習慣?),所以只能試試自己硬幹...沒想到還可以動就是了XD


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1
小碼農米爾
iT邦高手 1 級 ‧ 2020-08-19 15:58:31

雖然沒有看懂,但感覺是個厲害的東西

0
一級屠豬士
iT邦大師 1 級 ‧ 2020-08-19 16:07:19

咖喱化 ..... 一般是說 柯里化

fillano iT邦超人 1 級 ‧ 2020-08-19 16:14:37 檢舉

我是想反正都同一個字XD...肚子有點餓

4
良葛格
iT邦新手 2 級 ‧ 2020-08-19 18:22:48

路過補充一下… 嚴格來說,那是 Z Combinator,真正的 Y Combinator 在 JavaScript 環境中會因遞迴不會終止,最後達到 JavaScript 環境的遞迴堆疊上限,產生錯誤訊息才停止,因為 JavaScript 並不支援惰性求值。

可參考〈拖延的 Y〉。

看更多先前的回應...收起先前的回應...
fillano iT邦超人 1 級 ‧ 2020-08-19 19:43:45 檢舉

感謝補充,又學到新知了!

良葛格 iT邦新手 2 級 ‧ 2020-08-19 20:13:20 檢舉

有興趣深入的話,可以進一步參考《深入理解運算原理》這本書,裏頭有 lambda 演算入門,之前玩了一陣子,做了個小語言,底下的 lambda 函式相當於執行了 [1, 2, 3].map(elem => elem - 1):

(f => (x => f(n => x(x)(n)))(x => f(n => x(x)(n))))(m => l => f => (_ => _)((l => l(_ => _ => (_ => f => f(_ => _))))(l))(_ => (_ => (f => _ => f(_ => _))))(_ => (h => t => (l => r => f => f(l)(r))(h)(t))(f((p => p(l => _ => l))(l)))(m((p => p(_ => r => r))(l))(f))))((e => (l => (f => (x => f(n => x(x)(n)))(x => f(n => x(x)(n))))(v => r => l => (_ => _)((l => l(_ => _ => (_ => f => f(_ => _))))(l))(_ => r)(_ => v((h => t => (l => r => f => f(l)(r))(h)(t))((p => p(l => _ => l))(l))(r))((p => p(_ => r => r))(l))))(_ => (f => _ => f(_ => _)))(l))(e(_ => _ => (f => _ => f(_ => _)))))((f => (x => f(n => x(x)(n)))(x => f(n => x(x)(n))))(r => t => h => (_ => _)((n => n(_ => (_ => f => f(_ => _)))(_ => f => f(_ => _)))(h))(_ => t)(_ => r((h => t => (l => r => f => f(l)(r))(h)(t))(h)(t))))(_ => (f => _ => f(_ => _)))(f => x => f(x))(f => x => f(f(x)))(f => x => f(f(f(x))))))(e => (m => n => n(n => (p => p(l => _ => l))(n(p => (l => r => f => f(l)(r))((p => p(_ => r => r))(p))((n => f => x => f(n(f)(x)))((p => p(_ => r => r))(p))))((l => r => f => f(l)(r))(_ => _)(_ => x => x))))(m))(e)(f => x => f(x)))

如果以上函式指定給 lt,那麼使用底下這個函式(就像是 lambda 演算式的執行器),可以算出 [ 0, 1, 2 ]:

function array(lt) {
    let unit = _ => _;
    let no_use = unit;

    let no = _ => f => f(no_use); 
    let when = unit;

    let left = p => p(l => _ => l);
    let right = p => p(_ => r => r);

    let head = left;
    let tail = right;

    let is_nil = l => l(_ => _ => no);
    let isEmpty = is_nil;
        
    function natural(n) {
        return n(i => i + 1)(0);
    }

    function arr(acc, l) {
        return when(isEmpty(l))
                   (() => acc)
                   (() => arr(acc.concat([natural(head(l))]), tail(l)));
    }
    return arr([], lt);
}

let lt =  (f => (x => f(n => x(x)(n)))(x => f(n => x(x)(n))))(m => l => f => (_ => _)((l => l(_ => _ => (_ => f => f(_ => _))))(l))(_ => (_ => (f => _ => f(_ => _))))(_ => (h => t => (l => r => f => f(l)(r))(h)(t))(f((p => p(l => _ => l))(l)))(m((p => p(_ => r => r))(l))(f))))((e => (l => (f => (x => f(n => x(x)(n)))(x => f(n => x(x)(n))))(v => r => l => (_ => _)((l => l(_ => _ => (_ => f => f(_ => _))))(l))(_ => r)(_ => v((h => t => (l => r => f => f(l)(r))(h)(t))((p => p(l => _ => l))(l))(r))((p => p(_ => r => r))(l))))(_ => (f => _ => f(_ => _)))(l))(e(_ => _ => (f => _ => f(_ => _)))))((f => (x => f(n => x(x)(n)))(x => f(n => x(x)(n))))(r => t => h => (_ => _)((n => n(_ => (_ => f => f(_ => _)))(_ => f => f(_ => _)))(h))(_ => t)(_ => r((h => t => (l => r => f => f(l)(r))(h)(t))(h)(t))))(_ => (f => _ => f(_ => _)))(f => x => f(x))(f => x => f(f(x)))(f => x => f(f(f(x))))))(e => (m => n => n(n => (p => p(l => _ => l))(n(p => (l => r => f => f(l)(r))((p => p(_ => r => r))(p))((n => f => x => f(n(f)(x)))((p => p(_ => r => r))(p))))((l => r => f => f(l)(r))(_ => _)(_ => x => x))))(m))(e)(f => x => f(x)));

array(lt); // [0, 1, 2]

之前玩的時候留下的筆記…〈非關語言: 運算隨想

fillano iT邦超人 1 級 ‧ 2020-08-20 09:02:40 檢舉

太棒了,我來看看,感謝!

不懂,純拜見兩位大神
/images/emoticon/emoticon42.gif

我要留言

立即登入留言